﻿using System;
using System.IO;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Windows;
using System.Runtime.CompilerServices;

namespace CS633_Final_Project
{
    /// <summary>
    /// Represents the visibility based pursuit-evasion problem environment.
    /// </summary>
    class Environment
    {
        private ArrayList vertices;
        private GraphicsPath polygon;
        private ArrayList triangles;
        private DualGraph triangleDualGraph;
        private ArrayList edges;
        private Robot evader;
        private ArrayList pursuers;
        private const float SCALING_FACTOR = 43;

        /// <summary>
        /// Default Constructor
        /// </summary>
        public Environment()
        {
            vertices = new ArrayList();
            polygon = new GraphicsPath();
            edges = new ArrayList();
            triangleDualGraph = new DualGraph();
        }

        /// <summary>
        /// Creates a polygon from the given file.
        /// </summary>
        /// <param name="file">Filename containing the polygon.</param>
        /// <param name="left_margin">Left margin.</param>
        /// <param name="top_margin">Top margin.</param>
        public Environment(String file, float left_margin, float top_margin)
        {
            vertices = new ArrayList();
            polygon = new GraphicsPath();
            edges = new ArrayList();
            triangleDualGraph = new DualGraph();
            StreamReader reader = null;
            try
            {
                reader = new StreamReader(file);
                string s = "";

                int i = 0;
                while ((s = reader.ReadLine()) != null)
                {
                    String[] toks = s.Split(' ');
                    if (toks.Length == 2)
                    {
                        PointF vertex = new PointF();
                        vertex.X = float.Parse(toks[0]) * SCALING_FACTOR + left_margin;
                        vertex.Y = float.Parse(toks[1]) * SCALING_FACTOR + top_margin;

                        vertices.Add(vertex);

                        if (i != 0)
                        {
                            PointF previousVertex = (PointF)vertices.ToArray()[i - 1];
                            Line edge = new Line(previousVertex, vertex);
                            edges.Add(edge);
                        }
                        i++;
                    }
                }
                polygon.AddPolygon((PointF[])vertices.ToArray(typeof(PointF)));
                polygon.CloseAllFigures();

                Line e = new Line((PointF)vertices.ToArray()[0], (PointF)vertices.ToArray()[vertices.Count - 1]);
                edges.Add(e);

                GenerateTriangulation();
            }
            catch (Exception e)
            {
                Console.WriteLine(e.StackTrace);
            }
            finally
            {
                reader.Close();
            }
        }

        /// <summary>
        /// String representation of the polygon.
        /// </summary>
        /// <returns>String representation of the polygon.</returns>
        public override string ToString()
        {
            StringBuilder s = new StringBuilder();
            foreach (PointF vertex in vertices)
                s.Append(vertex.ToString() + "\n");

            return s.ToString();
        }

        /// <summary>
        /// Vertices of the polygon.
        /// </summary>
        public ArrayList Vertices
        {
            get { return this.vertices; }
            set { this.vertices = value; }
        }

        /// <summary>
        /// Polygon object.
        /// </summary>
        public GraphicsPath Polygon
        {
            get { return this.polygon; }
            set { this.polygon = value; }
        }

        /// <summary>
        /// Triangulated environment.
        /// </summary>
        public ArrayList Triangles
        {
            get { return this.triangles; }
            set { this.triangles = value; }
        }

        /// <summary>
        /// The Triangle Dual Graph
        /// </summary>
        public DualGraph TriangleDualGraph
        {
            get { return this.triangleDualGraph; }
            set { this.triangleDualGraph = value; }
        }

        /// <summary>
        /// The edges of the environment.
        /// </summary>
        public ArrayList Edges
        {
            get { return this.edges; }
            set { this.edges = value; }
        }

        /// <summary>
        /// The evader.
        /// </summary>
        public Robot Evader
        {
            [MethodImpl(MethodImplOptions.Synchronized)]
            get { return this.evader; }
            [MethodImpl(MethodImplOptions.Synchronized)]
            set { this.evader = value; }
        }

        /// <summary>
        /// The pursuers.
        /// </summary>
        public ArrayList Pursuers
        {
            get { return this.pursuers; }
            set { this.pursuers = value; }
        }

        /// <summary>
        /// Gets the points representing the polygon environment.
        /// </summary>
        /// <returns>Points representing the polygon environment.</returns>
        public PointF[] GetPoints()
        {
            PointF[] points = (PointF[])vertices.ToArray(typeof(PointF));
            return points;
        }

        /// <summary>
        /// Returns whether or not the point is contained within the environment.
        /// </summary>
        /// <param name="point">Point to test.</param>
        /// <returns>True if the point is in the environment, false otherwise.</returns>
        /// 
        [MethodImpl(MethodImplOptions.Synchronized)]
        public bool InEnvironment(PointF point)
        {
            return polygon.IsVisible(point);
        }

        /// <summary>
        /// Intialize all the robots in the environment to random positions.
        /// </summary>
        /// <param name="numPursuers">Number of pursuers to generate.</param>
        public void InitializeRobots(int numPursuers)
        {
            evader = new Robot();
            evader.Role = Robot.Type.EVADER;
            evader.Id = -1;
            InitializeEvader();

            pursuers = new ArrayList();            
            InitializePursuers(numPursuers);
        }

        /// <summary>
        /// Determines whether the pursuer can see the evader.
        /// </summary>
        /// <param name="pursuer">The pursuer.</param>
        /// <returns>True if the evader is visible, false otherwise.</returns>
        [MethodImpl(MethodImplOptions.Synchronized)]
        public bool SeeEvader(Robot pursuer)
        {
            bool see = true;

            Line visibilityPath = new Line(pursuer.Location, evader.Location);

            foreach (Line edge in edges)
            {
                PointF intersection = MathUtil.Intersection(visibilityPath, edge);
                if (!intersection.X.Equals(float.NegativeInfinity) && !intersection.Y.Equals(float.NegativeInfinity))
                {
                    see = false;
                    break;
                }
            }

            return see;
        }

        [MethodImpl(MethodImplOptions.Synchronized)]
        public bool SeePursuer()
        {
            bool see = false;
            foreach (Robot pursuer in pursuers)
            {
                see = SeeEvader(pursuer);
                if (see)
                    break;
            }
            return see;
        }

        /// <summary>
        /// Returns whether a given path is valid or not.
        /// </summary>
        /// <param name="start">Start Point</param>
        /// <param name="end">End Point</param>
        /// <returns></returns>
        public bool validPath(PointF start, PointF end)
        {
            bool valid = true;

            Line path = new Line(start, end);
            foreach (Line edge in Edges)
            {
                PointF intersection = MathUtil.Intersection(path, edge);
                if (intersection.X != float.NegativeInfinity && intersection.Y != float.NegativeInfinity)
                {
                    valid = false;
                    break;
                }
            }

            return valid;
        }

        private void GenerateTriangulation()
        {
            triangles = new ArrayList();
            ArrayList result = Triangulate.Process(vertices);
            int tcount = result.Count / 3;

            for (int i = 0; i < tcount; i++)
            {
                PointF p1 = (PointF)result.ToArray()[i * 3 + 0];
                PointF p2 = (PointF)result.ToArray()[i * 3 + 1];
                PointF p3 = (PointF)result.ToArray()[i * 3 + 2];

                Triangle triangle = new Triangle(p1, p2, p3);
                triangle.Id = i;
                triangles.Add(triangle);

                // add the triangle to the dual graph.
                triangleDualGraph.AddTriangle(triangle);
            }
        }

        private void InitializePursuers(int numPursuers)
        {
            Random random = new Random();
            for (int i = 0; i <= numPursuers - 1; i++)
            {
                Robot pursuer = new Robot();
                pursuer.Role = Robot.Type.PURSUER;
                pursuer.Id = i;
                pursuer.LocationTriangle = (Triangle)triangles.ToArray()[random.Next(0, triangles.Count - 1)];
                pursuers.Add(pursuer);
            }
        }

        private void InitializeEvader()
        {
            Random random = new Random();

            RectangleF BOUNDING_RECTANGLE = polygon.GetBounds();
            int MAX_Y = (int)BOUNDING_RECTANGLE.Bottom;
            int MAX_X = (int)BOUNDING_RECTANGLE.Right;
            int MIN_Y = (int)BOUNDING_RECTANGLE.Top;
            int MIN_X = (int)BOUNDING_RECTANGLE.Left;

            PointF evaderLocation = new PointF();
            do
            {
                evaderLocation.X = random.Next(MIN_X, MAX_X);
                evaderLocation.Y = random.Next(MIN_Y, MAX_Y);
            } while (evaderLocation.IsEmpty || !InEnvironment(evaderLocation));
            evader.Location = evaderLocation;
        }
    }
}
